package cn.zmwh.im.server.netty;

import cn.hutool.extra.spring.SpringUtil;
import cn.zmwh.im.server.constant.BsKImConstant;
import cn.zmwh.im.server.constant.IMRedisKey;
import cn.zmwh.im.server.mq.dto.IMInfo;
import cn.zmwh.im.server.netty.processor.ImMessageProcessor;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.util.AttributeKey;
import io.netty.util.internal.ThrowableUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;

/**
 * WebSocket 长连接下 文本帧的处理器
 * 实现浏览器发送文本回写
 * 浏览器连接状态监控
 */
@Slf4j
public class IMChannelHandler extends SimpleChannelInboundHandler<IMInfo> {


    /**
     * 读取到消息后进行处理
     *
     * @param ctx      channel上下文
     * @param sendInfo 发送消息
     */
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, IMInfo sendInfo) {
        // 创建处理器进行处理
        String cmd = sendInfo.getCmd();
        ImMessageProcessor messageProcessor = SpringUtil.getBean(cmd + "Processor", ImMessageProcessor.class);
        if (messageProcessor == null) {
            log.error("没有对应的处理器" + cmd);
            return;
        }
        if (sendInfo.isAck()) {
            AttributeKey<Long> userIdAttr = AttributeKey.valueOf(BsKImConstant.CHANNEL_ATTR_KEY.USER_ID);
            Long uid = ctx.channel().attr(userIdAttr).get();
            String messageId = sendInfo.getMessageId();
            messageProcessor.ack(uid, messageId, sendInfo.getData());
        } else {
            IMInfo imInfoResult = messageProcessor.read(ctx, sendInfo);//处理业务
            if (imInfoResult == null) {
                log.info("不返回响应原因：结果是否为空：{}", sendInfo == null);
                return;
            }
            imInfoResult.setCmd(cmd);
            imInfoResult.setAck(true);
            ctx.writeAndFlush(imInfoResult);//返回响应
        }
    }

    /**
     * 出现异常的处理 打印报错日志
     *
     * @param ctx   channel上下文
     * @param cause 异常信息
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        log.error(ThrowableUtil.stackTraceToString(cause));
        log.error(cause.getMessage());
        //关闭上下文
        //ctx.close();
    }

    /**
     * 监控浏览器上线
     *
     * @param ctx channel上下文
     */
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) {
        log.info(ctx.channel().id().asLongText() + "连接");
    }

    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) {
        AttributeKey<Long> userIdAttr = AttributeKey.valueOf(BsKImConstant.CHANNEL_ATTR_KEY.USER_ID);
        Long uid = ctx.channel().attr(userIdAttr).get();
        AttributeKey<String> terminalAttr = AttributeKey.valueOf(BsKImConstant.CHANNEL_ATTR_KEY.TERMINAL_TYPE);
        String terminal = ctx.channel().attr(terminalAttr).get();
        Channel channel = UserChannelMap.getChannel(uid, terminal);
        if (channel != null && !ctx.channel().id().equals(channel.id())) {
            // 移除channel
            UserChannelMap.removeChannel(uid, terminal);
            // 用户下线
            String key = String.join(":", IMRedisKey.GAIN_SERVER_ID, uid.toString(), terminal.toString());
            RedisTemplate redisTemplate = SpringUtil.getBean(RedisTemplate.class);
            redisTemplate.delete(key);
            log.info("断开连接,userId:{},终端类型:{},{}", uid, terminal, ctx.channel().id().asLongText());
        }
    }

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if (evt instanceof IdleStateEvent) {
            IdleState state = ((IdleStateEvent) evt).state();
            if (state == IdleState.READER_IDLE) {
                // 在规定时间内没有收到客户端的上行数据, 主动断开连接
                AttributeKey<Long> attr = AttributeKey.valueOf(BsKImConstant.CHANNEL_ATTR_KEY.USER_ID);
                Long uid = ctx.channel().attr(attr).get();
                AttributeKey<String> terminalAttr = AttributeKey.valueOf(BsKImConstant.CHANNEL_ATTR_KEY.TERMINAL_TYPE);
                String terminal = ctx.channel().attr(terminalAttr).get();
                log.info("心跳超时，即将断开连接,用户id:{},终端类型:{} ", uid, terminal);
                ChannelUtil.close(ctx.channel(), "心跳超时");
            }
        } else {
            super.userEventTriggered(ctx, evt);
        }

    }
}